/*
 * NetCheckInApp.java
 */

package com.google.code.netcheckin.app;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeEvent;
import org.jdesktop.application.Application;
import org.jdesktop.application.SingleFrameApplication;
import java.io.FileReader;
import com.google.code.netcheckin.aprs.AprsPacketDataObject;
import com.google.code.netcheckin.aprs.Station;
import com.google.code.netcheckin.io.StationDbReader;
import com.google.code.netcheckin.io.StationDbWriter;
import com.google.code.netcheckin.util.StationCallSuffixCollator;
import com.google.code.netcheckin.xastir.ObjectXmitter;
import com.google.code.netcheckin.xastir.XastirUdpClient;
import java.io.FileWriter;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import java.util.Stack;
import java.io.File;
import javax.swing.JOptionPane;

/**
 * The main class of the application.
 */
public class NetCheckInApp extends SingleFrameApplication {

    private NetCheckInView thisView;
    private Object[] stationObjects;
    private Preferences prefs = Preferences.userRoot().node("/netcheckin");
    private String xastirLogin;
    private String xastirPassword;
    private String xastirHostname;
    private int xastirPort;
    private String userHome = System.getProperty("user.home");
    
    @Override protected void initialize(String[] args) {
        readPrefs();
        PreferenceChangeListener preferenceChangeListener = 
            new PreferenceChangeListener() {
                public void preferenceChange(PreferenceChangeEvent event) {
                // refresh the preferences used by the app
                readPrefs();
                }
            };
        prefs.addPreferenceChangeListener(preferenceChangeListener);
        
        // Load the existing stations from .csv
        firstRunCheck();
        loadPredefinedStations();
    }
    
    /**
     * At startup create and show the main frame of the application.
     */
    @Override protected void startup() {
        
        thisView = new NetCheckInView(this);
        if (stationObjects != null) {
            showPredefinedStations(stationObjects);            
        }

        show(thisView);
        
    }
    
    /**
     * This method is to initialize the specified window by injecting resources.
     * Windows shown in our application come fully initialized from the GUI
     * builder, so this additional configuration is not needed.
     */
    @Override protected void configureWindow(java.awt.Window root) {
    }

    /**
     * A convenient static getter for the application instance.
     * @return the instance of NetCheckInApp
     */
    public static NetCheckInApp getApplication() {
        return Application.getInstance(NetCheckInApp.class);
    }

    /**
     * Loads the stations from a csv file into the stationsObject array
     */
    public void loadPredefinedStations() {
        String stationsFile = userHome + "/.netcheckin/stations.csv";
        try {
            // TODO: roll this into an application option
            StationDbReader sdr = new StationDbReader(new FileReader(stationsFile));
            stationObjects = sdr.read(new StationCallSuffixCollator());
            
        } catch (NullPointerException ex) {
            // Must be a new or empty file, nothing to load in
        } catch (FileNotFoundException ex) {
            // Logger.getLogger(NetCheckInApp.class.getName()).log(Level.SEVERE, null, ex);
            
            // go ahead and create the file and notify the user
            createStationsFile();
            welcomeNewUser();
        } catch (IOException ex) {
            Logger.getLogger(NetCheckInApp.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public void showPredefinedStations() {
        showPredefinedStations(stationObjects);
    }
    
    /**
     * Sorts through the stored stations array and displays them into the
     * predefined stations list
     * @param stations array of station objects
     */
    public void showPredefinedStations(Object[] stations) {
        // TODO setup objects for the list view instead of listing callsigns
        thisView.clearPredefinedCalls();
        for (Object stationObject : stations) {
            Station station = (Station)stationObject;
            thisView.addPredefinedCall(station.getCallsign());
        }
    }
    
    /**
     * Gives the current array of stations as an Object array
     * @return Object array containing Station objects
     */
    public Object[] getStationObjects() {
        return stationObjects;
    }
    
    /**
     * sets the object array of stations to the given object array
     * @param stations Object array of Station objects
     */
    public void setStationObjects(Object[] stations) {
        this.stationObjects = stations;
    }
    
    /**
     * takes the currently selected station in the predefined list and save
     * the current input fields (possibly edited) and saves them to the station
     * array.  Should be followed by a saveAll() to write to disk.
     * @param location The location within the array to update.
     */
    public void updateStationObjects(int location) {
        Station station = new Station();
        
        thisView.resetMissingInfo();
        
        // Fill the station with the current fields
        station.setCallsign(thisView.getCallsign());
        station.setCellphone(thisView.getCellPhone());
        station.setEmail(thisView.getEmail());
        station.setIsAres(thisView.isAres());
        station.setIsRaces(thisView.isRaces());
        station.setName(thisView.getStationName());
        station.setPrimaryAddress(thisView.getPrimaryAddress());
        station.setPrimaryCity(thisView.getPrimaryCity());
        station.setPrimaryLatitude1(thisView.getPrimaryLatDegrees());
        station.setPrimaryLatitude2(thisView.getPrimaryLatMinutes());
        station.setPrimaryLongitude1(thisView.getPrimaryLongDegrees());
        station.setPrimaryLongitude2(thisView.getPrimaryLongMinutes());
        station.setPrimaryState(thisView.getPrimaryState());
        station.setPrimaryZip(thisView.getPrimaryZip());
        station.setSecondaryAddress(thisView.getSecondaryAddress());
        station.setSecondaryCity(thisView.getSecondaryCity());
        station.setSecondaryLatitude1(thisView.getSecondaryLatDegrees());
        station.setSecondaryLatitude2(thisView.getSecondaryLatMinutes());
        station.setSecondaryLongitude1(thisView.getSecondaryLongDegrees());
        station.setSecondaryLongitude2(thisView.getSecondaryLongMinutes());
        station.setSecondaryState(thisView.getSecondaryState());
        station.setSecondaryZip(thisView.getSecondaryZip());
        station.setPrimaryLatitudeOrientation(thisView.getPrimaryLatOrientation());
        station.setSecondaryLatitudeOrientation(thisView.getSecondaryLatOrientation());
        station.setPrimaryLongitudeOrientation(thisView.getPrimaryLongOrientation());
        station.setSecondaryLongitudeOrientation(thisView.getSecondaryLongOrientation());
        station.setPrimaryIcon(thisView.getPrimaryIcon());
        station.setSecondaryIcon(thisView.getSecondaryIcon());
        
        if (thisView.isMissingInfo()) {
            missingInfoNotification(thisView.getMissingInfo());
        }
        
        // We need to update the station within the GUI, or add it if it does
        // not already exist.
        if (stationObjects.length != 0) {
            // temp testing hack until I get code up for the icons
            // TODO: write the icon handling code
            Station oldstation = (Station)stationObjects[location];
            //station.setPrimaryIcon(oldstation.getPrimaryIcon());
            //station.setSecondaryIcon(oldstation.getSecondaryIcon());
            // end hack

            stationObjects[location] = station;
        } else {
            stationObjects[0] = station;
        }
    }
    
    public boolean objectToXastir(Station station, boolean usePrimary) {
        if (xastirLogin.equals("SETME") || xastirPassword.equals("SETME")) {
            missingPreferences();
            return false;
        } else {
            try {
                ObjectXmitter xmitter = new ObjectXmitter(xastirHostname, xastirPort, xastirLogin, xastirPassword);
                int xmitResult = xmitter.xmit(station, usePrimary, AprsPacketDataObject.OBJECT_CREATE);
                if (xmitResult == XastirUdpClient.XASTIR_ACK) {
                    thisView.setStatusMessage("Station " + 
                            station.getCallsign() + " - " +
                            station.getName() + " checked in.");
                    return true;
                } else {
                    thisView.setStatusMessage("Error occurred when communicating" +
                            " with Xastir - Check your login settings " +
                            "under File - Preferences");
                    return false;
                }
            } catch (UnknownHostException ex) {
                Logger.getLogger(NetCheckInApp.class.getName()).log(Level.SEVERE, null, ex);
            } catch (SocketException ex) {
                Logger.getLogger(NetCheckInApp.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(NetCheckInApp.class.getName()).log(Level.SEVERE, null, ex);
            }
            return false;
        }
    }
    
    /**
     * Saves the current station objects to a file
     */
    public void saveAll() {
        String stationsFile = userHome + "/.netcheckin/stations.csv";
        try {
            StationDbWriter sdw = new StationDbWriter(new FileWriter(stationsFile));
            sdw.write(stationObjects);
            sdw.close();
            showPredefinedStations();
            thisView.setStatusMessage("Changes have been saved.");
        } catch (IOException ex) {
            Logger.getLogger(NetCheckInApp.class.getName()).log(Level.SEVERE, null, ex);
            thisView.setStatusMessage("Save Changes FAILED! Check the log.");
        }
    }
    
    /**
     * read the application preferences from Java Preferences
     */
    public void readPrefs() {
        xastirLogin = prefs.get("callsign", "SETME");
        xastirPassword = prefs.get("password", "SETME");
        xastirHostname = prefs.get("serverAddress", "localhost");
        xastirPort = prefs.getInt("serverPort", 2023);
    }
    
    /**
     * check for storage directory and basic prefs, create and notify as
     * necessary
     */
    public void firstRunCheck() {
        // check and create the data directory
        createNetcheckinDir();
        
        // check and create the stations.csv file
        createStationsFile();
        
        //welcomeNewUser();
    }
    
    /**
     * checks for the netcheckin directory and creates it if it is missing
     */
    public void createNetcheckinDir() {
        String netcheckinHome = userHome + "/.netcheckin";
        File dir = new File(netcheckinHome);
        if (!dir.exists()) {
            dir.mkdir();
        }
    }
    
    /**
     * checks for the netcheckin stations.csv file and creates if it is missing
     */
    public void createStationsFile() {
        String netcheckinHome = userHome + "/.netcheckin";
        // check and create the data directory first
        createNetcheckinDir();
        File f = new File(netcheckinHome + "/stations.csv");
        if (!f.exists()) {
            try {
                    boolean result = f.createNewFile();
                } catch (IOException ex) {
                    Logger.getLogger(NetCheckInApp.class.getName()).log(Level.SEVERE, null, ex);
                }
            welcomeNewUser();
            thisView.setStatusMessage("New stations data file generated.");
        }        
    }
    
    /**
     * new user popup window
     */
    public void welcomeNewUser() {
        JOptionPane.showMessageDialog(NetCheckInApp.getApplication().getMainFrame(),
                    "Welcome to NetCheckIn! No stations file was found, so you\n" +
                    " may be new here. You will need to configure your login and\n " +
                    "password for the program to be able to talk with Xastir.\n" +
                    " This is found in File -> Preferences", "Welcome to " +
                    "NetCheckIn for Xastir", JOptionPane.INFORMATION_MESSAGE);
    }
    
    /**
     * Notify the user via a popup that some critical information is missing
     * from the station that was saved.
     * @param whatsMissing Stack of strings identifying what parts are missing
     */
    public void missingInfoNotification(Stack whatsMissing) {
        String missingList = "";
        while (!whatsMissing.empty()) {
            missingList += whatsMissing.pop() + "\n";
        }
        JOptionPane.showMessageDialog(NetCheckInApp.getApplication().getMainFrame(),
                "You seem to be missing or have invalid entries for the \n" +
                "following fields which may be important to the usefulness of\n" +
                "NetCheckIn. The station has been saved without this info and\n" +
                "in some cases default values have been used. \n" +
                "You may wish to go back and fix these later:\n\n" 
                + missingList, "Missing Information",
                JOptionPane.ERROR_MESSAGE);
    }
    
    /**
     * Notify the user that they do not have the settings needed to send objects
     * to Xastir.
     */
    public void missingPreferences() {
        JOptionPane.showMessageDialog(NetCheckInApp.getApplication().getMainFrame(),
                "Your Xastir login and/or password have not been set. Please go\n" +
                "to File -> Preferences to set your callsign and APRS password,\n" +
                "else Xastir will not accept the objects sent from NetCheckIn.",
                "Missing Xastir Settings",
                JOptionPane.ERROR_MESSAGE);
    }
    
    /**
     * Main method launching the application.
     */
    public static void main(String[] args) {
        launch(NetCheckInApp.class, args);
    }
}
